package predicates

import (
	"github.com/evanphx/json-patch"
	jsonpatchv2 "gomodules.xyz/jsonpatch/v2"
	confv1beta1 "gopkg.openfuyao.cn/bkecommon/cluster/api/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/json"
	"sigs.k8s.io/controller-runtime/pkg/event"
	"sigs.k8s.io/controller-runtime/pkg/predicate"

	bkev1beta1 "gopkg.openfuyao.cn/cluster-api-provider-bke/api/v1beta1"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/annotation"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/condition"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/config"
	l "gopkg.openfuyao.cn/cluster-api-provider-bke/utils/log"
)

func BKEAgentReady() predicate.Funcs {
	return predicate.Funcs{
		UpdateFunc: func(e event.UpdateEvent) bool {
			if newObj := e.ObjectNew.(*bkev1beta1.BKECluster); newObj != nil {
				angent := condition.HasConditionStatus(bkev1beta1.BKEAgentCondition, newObj, confv1beta1.ConditionTrue) && condition.HasConditionStatus(bkev1beta1.NodesEnvCondition, newObj, confv1beta1.ConditionTrue)
				requeue := condition.HasConditionStatus(bkev1beta1.TargetClusterBootCondition, newObj, confv1beta1.ConditionFalse)
				return angent || requeue
			}
			return false
		},
		CreateFunc:  func(event.CreateEvent) bool { return false },
		DeleteFunc:  func(event.DeleteEvent) bool { return false },
		GenericFunc: func(event.GenericEvent) bool { return false },
	}
}

func BKEClusterUnPause() predicate.Funcs {
	return predicate.Funcs{
		UpdateFunc: func(e event.UpdateEvent) bool {
			newObj, ok := e.ObjectNew.(*bkev1beta1.BKECluster)
			if !ok {
				return false
			}
			return !newObj.Spec.Pause
		},
		CreateFunc: func(e event.CreateEvent) bool {
			obj, ok := e.Object.(*bkev1beta1.BKECluster)
			if !ok {
				return false
			}
			return !obj.Spec.Pause
		},
		DeleteFunc:  func(event.DeleteEvent) bool { return true },
		GenericFunc: func(event.GenericEvent) bool { return false },
	}
}

func BKEClusterSpecChange() predicate.Funcs {
	return predicate.Funcs{
		UpdateFunc: func(e event.UpdateEvent) bool {
			newObj, ok := e.ObjectNew.(*bkev1beta1.BKECluster)
			if !ok {
				return false
			}
			oldObj, ok := e.ObjectOld.(*bkev1beta1.BKECluster)
			if !ok {
				return false
			}
			log := l.With("bkecluster", utils.ClientObjNS(newObj))

			if newObj != nil && oldObj != nil {
				if newObj.Generation != oldObj.Generation {
					patches, _ := getCurrentBkeClusterPatches(oldObj.DeepCopy(), newObj.DeepCopy())
					for _, patch := range patches {
						kind := patch.Kind()
						path, _ := patch.Path()
						value, _ := patch.ValueInterface()
						//log.Debugf("%s path：%s, value：%v", patch.Kind(), path, value)
						log.Debugf("%s path: %s, value: %v", kind, path, value)
					}
					log.Debugf("cluster spec change, generation: %d, new: %d, old: %d", newObj.Generation, newObj.Generation, oldObj.Generation)

					if !newObj.ObjectMeta.DeletionTimestamp.IsZero() || !oldObj.ObjectMeta.DeletionTimestamp.IsZero() {
						log.Infof("bkecluster 正在删除")
						return true
					}

					if oldObj.Spec.Pause != newObj.Spec.Pause {
						log.Infof("集群暂停状态变更 %v -> %v", oldObj.Spec.Pause, newObj.Spec.Pause)
						return true
					}

					if config.EnableInternalUpdate {
						if _, ok := condition.HasCondition(bkev1beta1.InternalSpecChangeCondition, newObj); ok {
							log.Infof("内部修改spec内容，跳过入队")
							return false
						}
					}

					// 如果在deploying中，所有更新不入队
					if newObj.Status.ClusterHealthState == bkev1beta1.Deploying {
						log.Debugf("集群状态为deploying，跳过入队")
						return false
					}

					return true
				}
			}

			return false
		},
		CreateFunc: func(e event.CreateEvent) bool {
			obj, ok := e.Object.(*bkev1beta1.BKECluster)
			if !ok {
				return false
			}
			return obj != nil
		},
	}
}

func BKEClusterAnnotationsChange() predicate.Funcs {
	return predicate.Funcs{
		UpdateFunc: func(e event.UpdateEvent) bool {
			newObj, ok := e.ObjectNew.(*bkev1beta1.BKECluster)
			if !ok {
				return false
			}
			oldObj, ok := e.ObjectOld.(*bkev1beta1.BKECluster)
			if !ok {
				return false
			}

			allowChangeAnnotations := []string{
				annotation.AppointmentDeletedNodesAnnotationKey,
				annotation.AppointmentAddNodesAnnotationKey,
				annotation.RetryAnnotationKey,
				annotation.ClusterTrackerHealthyCheckFailedAnnotationKey,
			}
			log := l.With("bkecluster", utils.ClientObjNS(newObj))
			for _, key := range allowChangeAnnotations {
				newV, newFound := annotation.HasAnnotation(newObj, key)
				oldV, oldFound := annotation.HasAnnotation(oldObj, key)
				if (newV != oldV) || (newFound && !oldFound) {
					log.Infof("cluster annotations change, key: %s, new: %v, old: %v", key, newV, oldV)
					return true
				}
			}
			return false
		},
	}
}

func getCurrentBkeClusterPatches(old, new *bkev1beta1.BKECluster) (jsonpatch.Patch, error) {
	//去除last-update注释
	annotation.RemoveAnnotation(old, annotation.LastUpdateConfigurationAnnotationKey)
	annotation.RemoveAnnotation(new, annotation.LastUpdateConfigurationAnnotationKey)

	// 只考虑spec，labels，annotations,finalizers
	// 清空其余字段
	old.ObjectMeta = metav1.ObjectMeta{
		Labels:          old.ObjectMeta.Labels,
		Annotations:     old.ObjectMeta.Annotations,
		ResourceVersion: old.ObjectMeta.ResourceVersion,
	}
	new.ObjectMeta = metav1.ObjectMeta{
		Labels:          new.ObjectMeta.Labels,
		Annotations:     new.ObjectMeta.Annotations,
		ResourceVersion: new.ObjectMeta.ResourceVersion,
	}
	// 忽略status
	old.Status = confv1beta1.BKEClusterStatus{}
	new.Status = confv1beta1.BKEClusterStatus{}

	oldBytes, err := json.Marshal(old)
	if err != nil {
		return nil, err
	}
	newBytes, err := json.Marshal(new)
	if err != nil {
		return nil, err
	}

	patches, err := jsonpatchv2.CreatePatch(oldBytes, newBytes)
	if err != nil {
		return nil, err
	}

	if patches == nil || len(patches) == 0 {
		return nil, nil
	}

	pBytes, err := json.Marshal(patches)
	if err != nil {
		return nil, err
	}

	decodePatch, err := jsonpatch.DecodePatch(pBytes)
	if err != nil {
		return nil, err
	}
	return decodePatch, nil
}
